/*
 * Copyright (c) 2007, 2015, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 */
/*
 * Copyright 2001-2004 The Apache Software Foundation.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
/*
 * $Id: ToSAXHandler.java,v 1.2.4.1 2005/09/22 11:03:15 pvedula Exp $
 */
package com.sun.org.apache.xml.internal.serializer;

import java.util.Vector;

import org.xml.sax.Attributes;
import org.xml.sax.ContentHandler;
import org.xml.sax.ErrorHandler;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.xml.sax.ext.LexicalHandler;

/**
 * This class is used to provide a base behavior to be inherited
 * by other To...SAXHandler serializers.
 *
 * This class is not a public API.
 *
 * @xsl.usage internal
 */
public abstract class ToSAXHandler extends SerializerBase {

  public ToSAXHandler() {
  }

  public ToSAXHandler(
      ContentHandler hdlr,
      LexicalHandler lex,
      String encoding) {
    setContentHandler(hdlr);
    setLexHandler(lex);
    setEncoding(encoding);
  }

  public ToSAXHandler(ContentHandler handler, String encoding) {
    setContentHandler(handler);
    setEncoding(encoding);
  }

  /**
   * Underlying SAX handler. Taken from XSLTC
   */
  protected ContentHandler m_saxHandler;

  /**
   * Underlying LexicalHandler. Taken from XSLTC
   */
  protected LexicalHandler m_lexHandler;

  /**
   * A startPrefixMapping() call on a ToSAXHandler will pass that call
   * on to the wrapped ContentHandler, but should we also mirror these calls
   * with matching attributes, if so this field is true.
   * For example if this field is true then a call such as
   * startPrefixMapping("prefix1","uri1") will also cause the additional
   * internally generated attribute xmlns:prefix1="uri1" to be effectively added
   * to the attributes passed to the wrapped ContentHandler.
   */
  private boolean m_shouldGenerateNSAttribute = true;

  /**
   * If this is true, then the content handler wrapped by this
   * serializer implements the TransformState interface which
   * will give the content handler access to the state of
   * the transform.
   */
  protected TransformStateSetter m_state = null;

  /**
   * Pass callback to the SAX Handler
   */
  protected void startDocumentInternal() throws SAXException {
    if (m_needToCallStartDocument) {
      super.startDocumentInternal();

      m_saxHandler.startDocument();
      m_needToCallStartDocument = false;
    }
  }

  /**
   * Do nothing.
   *
   * @see org.xml.sax.ext.LexicalHandler#startDTD(String, String, String)
   */
  public void startDTD(String arg0, String arg1, String arg2)
      throws SAXException {
    // do nothing for now
  }

  /**
   * Receive notification of character data.
   *
   * @param characters The string of characters to process.
   * @see ExtendedContentHandler#characters(String)
   */
  public void characters(String characters) throws SAXException {
    final int len = characters.length();
    if (len > m_charsBuff.length) {
      m_charsBuff = new char[len * 2 + 1];
    }
    characters.getChars(0, len, m_charsBuff, 0);
    characters(m_charsBuff, 0, len);
  }

  /**
   * Receive notification of a comment.
   *
   * @see ExtendedLexicalHandler#comment(String)
   */
  public void comment(String comment) throws SAXException {
    flushPending();

    // Ignore if a lexical handler has not been set
    if (m_lexHandler != null) {
      final int len = comment.length();
      if (len > m_charsBuff.length) {
        m_charsBuff = new char[len * 2 + 1];
      }
      comment.getChars(0, len, m_charsBuff, 0);
      m_lexHandler.comment(m_charsBuff, 0, len);
      // time to fire off comment event
      if (m_tracer != null) {
        super.fireCommentEvent(m_charsBuff, 0, len);
      }
    }

  }

  /**
   * Do nothing as this is an abstract class. All subclasses will need to
   * define their behavior if it is different.
   *
   * @see org.xml.sax.ContentHandler#processingInstruction(String, String)
   */
  public void processingInstruction(String target, String data)
      throws SAXException {
    // Redefined in SAXXMLOutput
  }

  protected void closeStartTag() throws SAXException {
  }

  protected void closeCDATA() throws SAXException {
    // Redefined in SAXXMLOutput
  }

  /**
   * Receive notification of the beginning of an element, although this is a
   * SAX method additional namespace or attribute information can occur before
   * or after this call, that is associated with this element.
   *
   * @throws org.xml.sax.SAXException Any SAX exception, possibly wrapping another exception.
   * @see org.xml.sax.ContentHandler#startElement
   * @see org.xml.sax.ContentHandler#endElement
   * @see org.xml.sax.AttributeList
   * @see org.xml.sax.ContentHandler#startElement(String, String, String, Attributes)
   */
  public void startElement(
      String arg0,
      String arg1,
      String arg2,
      Attributes arg3)
      throws SAXException {
    if (m_state != null) {
      m_state.resetState(getTransformer());
    }

    // fire off the start element event
    if (m_tracer != null) {
      super.fireStartElem(arg2);
    }
  }

  /**
   * Sets the LexicalHandler.
   *
   * @param _lexHandler The LexicalHandler to set
   */
  public void setLexHandler(LexicalHandler _lexHandler) {
    this.m_lexHandler = _lexHandler;
  }

  /**
   * Sets the SAX ContentHandler.
   *
   * @param _saxHandler The ContentHandler to set
   */
  public void setContentHandler(ContentHandler _saxHandler) {
    this.m_saxHandler = _saxHandler;
    if (m_lexHandler == null && _saxHandler instanceof LexicalHandler) {
      // we are not overwriting an existing LexicalHandler, and _saxHandler
      // is also implements LexicalHandler, so lets use it
      m_lexHandler = (LexicalHandler) _saxHandler;
    }
  }

  /**
   * Does nothing. The setting of CDATA section elements has an impact on
   * stream serializers.
   *
   * @see SerializationHandler#setCdataSectionElements(java.util.Vector)
   */
  public void setCdataSectionElements(Vector URI_and_localNames) {
    // do nothing
  }

  /**
   * Set whether or not namespace declarations (e.g.
   * xmlns:foo) should appear as attributes of
   * elements
   *
   * @param doOutputNSAttr whether or not namespace declarations should appear as attributes
   */
  public void setShouldOutputNSAttr(boolean doOutputNSAttr) {
    m_shouldGenerateNSAttribute = doOutputNSAttr;
  }

  /**
   * Returns true if namespace declarations from calls such as
   * startPrefixMapping("prefix1","uri1") should
   * also be mirrored with self generated additional attributes of elements
   * that declare the namespace, for example the attribute xmlns:prefix1="uri1"
   */
  boolean getShouldOutputNSAttr() {
    return m_shouldGenerateNSAttribute;
  }

  /**
   * This method flushes any pending events, which can be startDocument()
   * closing the opening tag of an element, or closing an open CDATA section.
   */
  public void flushPending() throws SAXException {

    if (m_needToCallStartDocument) {
      startDocumentInternal();
      m_needToCallStartDocument = false;
    }

    if (m_elemContext.m_startTagOpen) {
      closeStartTag();
      m_elemContext.m_startTagOpen = false;
    }

    if (m_cdataTagOpen) {
      closeCDATA();
      m_cdataTagOpen = false;
    }

  }

  /**
   * Pass in a reference to a TransformState object, which
   * can be used during SAX ContentHandler events to obtain
   * information about he state of the transformation. This
   * method will be called  before each startDocument event.
   *
   * @param ts A reference to a TransformState object
   */
  public void setTransformState(TransformStateSetter ts) {
    this.m_state = ts;
  }

  /**
   * Receives notification that an element starts, but attributes are not
   * fully known yet.
   *
   * @param uri the URI of the namespace of the element (optional)
   * @param localName the element name, but without prefix (optional)
   * @param qName the element name, with prefix, if any (required)
   * @see ExtendedContentHandler#startElement(String, String, String)
   */
  public void startElement(String uri, String localName, String qName)
      throws SAXException {

    if (m_state != null) {
      m_state.resetState(getTransformer());
    }

    // fire off the start element event
    if (m_tracer != null) {
      super.fireStartElem(qName);
    }
  }

  /**
   * An element starts, but attributes are not fully known yet.
   *
   * @param qName the element name, with prefix (if any).
   * @see ExtendedContentHandler#startElement(String)
   */
  public void startElement(String qName) throws SAXException {
    if (m_state != null) {
      m_state.resetState(getTransformer());
    }
    // fire off the start element event
    if (m_tracer != null) {
      super.fireStartElem(qName);
    }
  }

  /**
   * This method gets the node's value as a String and uses that String as if
   * it were an input character notification.
   *
   * @param node the Node to serialize
   */
  public void characters(org.w3c.dom.Node node)
      throws org.xml.sax.SAXException {
    // remember the current node
    if (m_state != null) {
      m_state.setCurrentNode(node);
    }

    // Get the node's value as a String and use that String as if
    // it were an input character notification.
    String data = node.getNodeValue();
    if (data != null) {
      this.characters(data);
    }
  }

  /**
   * @see org.xml.sax.ErrorHandler#fatalError(SAXParseException)
   */
  public void fatalError(SAXParseException exc) throws SAXException {
    super.fatalError(exc);

    m_needToCallStartDocument = false;

    if (m_saxHandler instanceof ErrorHandler) {
      ((ErrorHandler) m_saxHandler).fatalError(exc);
    }
  }

  /**
   * @see org.xml.sax.ErrorHandler#error(SAXParseException)
   */
  public void error(SAXParseException exc) throws SAXException {
    super.error(exc);

    if (m_saxHandler instanceof ErrorHandler) {
      ((ErrorHandler) m_saxHandler).error(exc);
    }

  }

  /**
   * @see org.xml.sax.ErrorHandler#warning(SAXParseException)
   */
  public void warning(SAXParseException exc) throws SAXException {
    super.warning(exc);

    if (m_saxHandler instanceof ErrorHandler) {
      ((ErrorHandler) m_saxHandler).warning(exc);
    }
  }


  /**
   * Try's to reset the super class and reset this class for
   * re-use, so that you don't need to create a new serializer
   * (mostly for performance reasons).
   *
   * @return true if the class was successfuly reset.
   * @see Serializer#reset()
   */
  public boolean reset() {
    boolean wasReset = false;
    if (super.reset()) {
      resetToSAXHandler();
      wasReset = true;
    }
    return wasReset;
  }

  /**
   * Reset all of the fields owned by ToSAXHandler class
   */
  private void resetToSAXHandler() {
    this.m_lexHandler = null;
    this.m_saxHandler = null;
    this.m_state = null;
    this.m_shouldGenerateNSAttribute = false;
  }

  /**
   * Add a unique attribute
   */
  public void addUniqueAttribute(String qName, String value, int flags)
      throws SAXException {
    addAttribute(qName, value);
  }
}
